/**
 * @ignore
 * Json.stringify for KISSY
 * @author yiminghe@gmail.com
 */
var Quote = require('./quote');
var util = require('util');

function padding2(n) {
    return n < 10 ? '0' + n : n;
}

function str(key, holder, replacerFunction, propertyList, gap, stack, indent) {
    var value = holder[key];
    if (value && typeof value === 'object') {
        if (typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        } else if (value instanceof Date) {
            value = isFinite(value.valueOf()) ?
                value.getUTCFullYear() + '-' +
                padding2(value.getUTCMonth() + 1) + '-' +
                padding2(value.getUTCDate()) + 'T' +
                padding2(value.getUTCHours()) + ':' +
                padding2(value.getUTCMinutes()) + ':' +
                padding2(value.getUTCSeconds()) + 'Z' : null;
        } else if (value instanceof  String || value instanceof  Number || value instanceof Boolean) {
            value = value.valueOf();
        }
    }
    if (replacerFunction !== undefined) {
        value = replacerFunction.call(holder, key, value);
    }

    switch (typeof value) {
        case 'number':
            return isFinite(value) ? String(value) : 'null';
        case 'string':
            return Quote.quote(value);
        case 'boolean':
            return String(value);
        case 'object':
            if (!value) {
                return 'null';
            }
            if (util.isArray(value)) {
                return ja(value, replacerFunction, propertyList, gap, stack, indent);
            }
            return jo(value, replacerFunction, propertyList, gap, stack, indent);
        // ignore undefined
    }

    return undefined;
}

function jo(value, replacerFunction, propertyList, gap, stack, indent) {
    if ('@DEBUG@') {
        if (util.inArray(value, stack)) {
            throw new TypeError('cyclic json');
        }
        stack[stack.length] = value;
    }

    var stepBack = indent;
    indent += gap;
    var k, kl, i, p;
    if (propertyList !== undefined) {
        k = propertyList;
    } else {
        k = util.keys(value);
    }
    var partial = [];
    for (i = 0, kl = k.length; i < kl; i++) {
        p = k[i];
        var strP = str(p, value, replacerFunction, propertyList, gap, stack, indent);
        if (strP !== undefined) {
            var member = Quote.quote(p);
            member += ':';
            if (gap) {
                member += ' ';
            }
            member += strP;
            partial[partial.length] = member;
        }
    }
    var ret;
    if (!partial.length) {
        ret = '{}';
    } else {
        if (!gap) {
            ret = '{' + partial.join(',') + '}';
        } else {
            var separator = ',\n' + indent;
            var properties = partial.join(separator);
            ret = '{\n' + indent + properties + '\n' + stepBack + '}';
        }
    }
    if ('@DEBUG@') {
        stack.pop();
    }
    return ret;
}

function ja(value, replacerFunction, propertyList, gap, stack, indent) {
    if ('@DEBUG@') {
        if (util.inArray(value, stack)) {
            throw new TypeError('cyclic json');
        }
        stack[stack.length] = value;
    }
    var stepBack = indent;
    indent += gap;
    var partial = [];
    var len = value.length;
    var index = 0;
    while (index < len) {
        var strP = str(String(index), value, replacerFunction, propertyList, gap, stack, indent);
        if (strP === undefined) {
            partial[partial.length] = 'null';
        } else {
            partial[partial.length] = strP;
        }
        ++index;
    }
    var ret;
    if (!partial.length) {
        ret = '[]';
    } else {
        if (!gap) {
            ret = '[' + partial.join(',') + ']';
        } else {
            var separator = '\n,' + indent;
            var properties = partial.join(separator);
            ret = '[\n' + indent + properties + '\n' + stepBack + ']';
        }
    }
    if ('@DEBUG@') {
        stack.pop();
    }

    return ret;
}

function stringify(value, replacer, space) {
    var gap = '';
    var propertyList, replacerFunction;
    if (replacer) {
        if (typeof replacer === 'function') {
            replacerFunction = replacer;
        } else if (util.isArray(replacer)) {
            propertyList = replacer;
        }
    }

    if (typeof space === 'number') {
        space = Math.min(10, space);
        gap = new Array(space + 1).join(' ');
    } else if (typeof space === 'string') {
        gap = space.slice(0, 10);
    }

    return str('', {
        '': value
    }, replacerFunction, propertyList, gap, [], '');
}

module.exports = stringify;

/**
 * @ignore
 * refer:
 *  - http://www.ecma-international.org/publications/standards/Ecma-262.htm
 *  - https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Json/stringify
 *  - http://www.json.org/
 *  - http://www.ietf.org/rfc/rfc4627.txt
 */